/**
 * @file src/datatypes/UndirectedGraph.java
 */
package datatypes;

/** imports */ 
import java.io.FileInputStream;
import java.util.Scanner;
import java.util.Vector;

/**
 * Class that represents a problem
 */
public class UndirectedGraph
{
    /** Initialize state */
    private boolean mInitialized;

    /** Set of actions(edges) */
    private Vector<Edge> mEdges;

    /** Set of states(nodes) */
    private Vector<Node> mNodes;

    /**
     *  Default constructor that creates an empty undirected graph
     */
    public UndirectedGraph()
    {
        mInitialized = false;
        mEdges = null;
        mNodes = null;
    }

    /** Getter */
    public Vector<Node> getNodes() { return mNodes; } 
    
    /**
     * Resets a problem/graph by reseting all nodes
     */
    public void reset()
    {
    	if (mInitialized) {
    		/** reset all nodes */
    		for (Node n : mNodes) { n.reset(); }
    	}

    } // reset()
    
    /**
     *  Initialize problem.
     *  Resets internal values of all nodes.
     *  Set depth/path costs of source node to zero.
     *  
     *  @param sourceState The source state
     *  @param goalState  The goal state
     */
    public void init(String sourceState, String goalState) throws Exception
    {
        if (!mInitialized) { throw new Exception("Graph is not yet initialized!"); }

        /** find source and goal node */
        Node mSourceNode = findNode(sourceState);
        Node mGoal = findNode(goalState);
        if (mSourceNode == null || mGoal == null) { throw new Exception("Source/Goal state not found."); }
        
        /** reset graph */
        reset();

        /** values for source state */
        mSourceNode.setPathCost(0.0f);

    } // init(String sourceState, String goalState)

    /**
     *  Successor function that provides all successors for a given node within a problem
     * 
     *  @param node Node which successors should be searched for
     *   
     *  @return List of all successors
     */
    public Vector<Tuple<Edge, Node>> getSuccessors(Node node) throws Exception
    {
        if (!mInitialized) { throw new Exception("Graph is not yet initialized!"); }
        Vector<Tuple<Edge, Node>> result = new Vector<Tuple<Edge, Node>>(0);
       
        /** search within all edges */
        for (Edge e : mEdges) {

        	/** decided to handle issues top-down, not bottom-up */
        	/** ignore edge where you come from */
        	// if (e == node.getPredecessorEdge()) { continue; }
        	
            Node adj = e.getAdjacentNode(node);
            /** Test, if adjacent node is found and ADDITIONALLY, if adjacent node is parent!! */
            if (adj != null /** && node.getParent() != adj */) {
                /** generate tuple of <ACTION, STATE> */
                Tuple<Edge, Node> tuple = new Tuple<Edge, Node>(e, adj);
                result.addElement(tuple);
            }
        }
        
        return result;

    } // getSuccessors(Node node)

    /**
     * Find node by a given state
     * 
     * @param state String representation of a state
     * 
     * @return Node the string representationn corresponds to
     */
    public Node findNode(String state) throws Exception
    {
        if (!mInitialized) { throw new Exception("Graph is not yet initialized!"); }

        for (Node n : mNodes) {
            if (n.getState().compareTo(state) == 0) { return n; }
        }
        return null;

    } // findNode(String state)

    /**
     * Find edge by given states
     * 
     * @param stateA Left/right state of a edge
     * @param stateB Left/right state of a edge
     * 
     * @return Edge the two states correspond to
     */
    public Edge findEdge(String stateA, String stateB) throws Exception
    {
        if (!mInitialized) { throw new Exception("Graph is not yet initialized!"); }

        /** get nodes */
        Node nodeA = findNode(stateA);
        Node nodeB = findNode(stateB);

        if (nodeA != null && nodeB != null) {
            
            /** search within all edges */
            for (Edge e : mEdges) {
                if (e.getAdjacentNode(nodeA) == nodeB) { return e; }
            }
        }
        
        return null;
        
    } // findEdge(String stateA, String stateB)

    /**
     * Read in a problem/graph from file
     * 
     * @param fn Filename of a problem
     */
    public void read(String fn) throws Exception
    {
        /** Initialize graph/problem */
        if (mInitialized) {
            mEdges.clear();
            mNodes.clear();
        } else {
            mEdges = new Vector<Edge>(0);
            mNodes = new Vector<Node>(0);
            mInitialized = true;
        }

        /** Find out system specific line separator */
        // final String NEWLINE = System.getProperty("line.separator");
        
        /** instantiate the actual scanner and input stream */
        Scanner scanner = new Scanner(new FileInputStream(fn));
        
        /** start reading file */
        try {
        	
        	while(true) {
        		
                /** first string, Node/State */
        		String nameNodeA = scanner.hasNext() ? scanner.next() : null;
        		if (nameNodeA == null) {
        			/** apparently reached end of file */
        			break;
        		}
                
                /** second string, Node/State */
                String nameNodeB = scanner.hasNext() ? scanner.next() : null;
                if (nameNodeB == null) { throw new Exception("No second node name provided!"); }

                /** third float, Step costs */
                float stepCosts = scanner.hasNextFloat() ? scanner.nextFloat() : Float.MAX_VALUE;
                if (stepCosts == Float.MAX_VALUE) { throw new Exception("No step costs provided!"); } 

                /** store data */
                
                /** get nodes, if existent */
                Node nodeA = findNode(nameNodeA);
                Node nodeB = findNode(nameNodeB);
                
                /** create nodes and add, if necessary */
                if (nodeA == null) {
                    nodeA = new Node(nameNodeA);
                    mNodes.addElement(nodeA);
                }
                if (nodeB == null) {
                    nodeB = new Node(nameNodeB);
                    mNodes.addElement(nodeB);
                }
                
                /** get edge, if existent */
                Edge edge = findEdge(nameNodeA, nameNodeB);
                
                /** create edge and add, if necessary */
                if (edge == null) {
                    edge = new Edge(stepCosts, nodeA, nodeB);
                    mEdges.addElement(edge);
                }
               
                if (scanner.hasNextLine()) {
                	/** go to next line */
                	scanner.nextLine();
                } else {
                	break;
                }
                
            } // while(true)
            
        } catch (Exception e) {
            System.out.println("UndirectedGraph::read(String fn) : " + e.toString());
        } finally {
            /** close scanner when finished */
            scanner.close();
        }
        
        /** trim size */
        mEdges.trimToSize();
        mNodes.trimToSize();
               
    } // read(String fn)
    
    /**
     * Checks a given heuristic
     * 
     * @param heur A given heuristic
     * 
     * @return
     */
    public boolean checkHeuristics(Heuristics heur, String goal)
    {
    	for (Node n: mNodes) {
    		/** get heuristic distance */
    		float distance = heur.get(n.getState());
    		
    		/** if distance is Float.MAX_VALUE || 0, it should be goal or source state */ 
    		if (distance == Float.MAX_VALUE || distance == 0.0f) {
    			if (n.getState().compareTo(goal) != 0) {
    				System.out.println("Failure in checkHeuristics: goal state does not correspond to given heuristic!");
    				return false;
    			}
    		}
    	} // for
    	
    	return true;
    } // checkHeuristics(Heuristics heur, String goal)
    
} // class UndirectedGraph
